package com.wbdca.crawler.entity;

import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.wbdca.crawler.common.ConnectionPool;
import com.wbdca.crawler.common.FineGrainedTokens;
import com.wbdca.crawler.common.Message;
import com.wbdca.crawler.util.CrawlerUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

@Getter
@Setter
@ToString
public class Repository {
    private final long id; // as of 14 Nov 2022, the maximum id of GitHub repositories is around 565,800,000
    private String name;
    private String fullName;
    private String description;
    private String url;
    private int forks;
    private int openIssues;
    private int watchers;
    private String license;
    private ArrayList<String> topics = new ArrayList<>();
    private ArrayList<User> contributors = new ArrayList<>();
    private HashMap<String, Integer> languages = new HashMap<>();
    private int contributorsCount;

    public void write() {
        File file = new File("./Datas/" + name + ".json");
        JsonWriter writer = null;
        try {
            writer = new JsonWriter(new FileWriter(file));
            writer.setIndent("\t");

            writer.beginObject();
            writer.name("name").value(name);
            writer.name("description").value(description);
            writer.name("forks").value(forks);
            writer.name("open_issues").value(openIssues);
            writer.name("watchers").value(watchers);
            writer.name("license").value(license);

            if(!topics.isEmpty())   writeTopics(writer);
            if(!languages.isEmpty())    writeLanguages(writer);
            if(!contributors.isEmpty()) writeContributors(writer);

            writer.endObject();
            writer.close();
        } catch (IOException e) {
            Message.warn(e.getMessage());
        }
    }

    public void writeProfile(JsonWriter writer) throws IOException {
        writer.beginObject();
        writer.name("name").value(name);
        writer.name("description").value(description);
        writer.name("forks").value(forks);
        writer.name("open_issues").value(openIssues);
        writer.name("stars").value(watchers);
        writer.name("license").value(license);
        writer.name("contributors_count").value(contributorsCount);
        writeLanguages(writer);
        writer.endObject();
    }

    private void writeTopics(JsonWriter writer) throws IOException {
        writer.name("topics");
        writer.beginArray();
        for (String topic : topics) {
            writer.value(topic);
        }
        writer.endArray();
    }

    private void writeLanguages(JsonWriter writer) throws IOException {
        writer.name("languages");
        writer.beginObject();
        for(Map.Entry<String, Integer> entry : languages.entrySet()){
            writer.name(entry.getKey()).value(entry.getValue());
        }
        writer.endObject();
    }

    private void writeContributors(JsonWriter writer) throws IOException {
        writer.name("contributors");
        writer.beginArray();
        for (User contributor : contributors) {
            writer.value(contributor.getLogin());
        }
        writer.endArray();
    }

    public void addTopic(String topic){
        this.topics.add(topic);
    }
    public void readLanguages(){
        JsonReader reader = null;
        try {
            CloseableHttpClient client = ConnectionPool.getClient();
            HttpGet httpGet = new HttpGet(new URIBuilder(this.getUrl())
                    .appendPath("languages").build());
            FineGrainedTokens.addAuthorization(httpGet);
            CloseableHttpResponse response = client.execute(httpGet);
            reader = new JsonReader(new InputStreamReader(response.getEntity().getContent()));
//            CloseableHttpResponse response = ConnectionPool.getResponse(new URIBuilder(this.getUrl())
//                    .appendPath("languages").build());
//            reader = new JsonReader(new InputStreamReader(response.getEntity().getContent()));
            reader.beginObject();
            while(reader.hasNext()){
                String language = reader.nextName();
                if (language.equals("message")) {
                    Message.warn(name + "read language error.");
                    return ;
                }
                int length = reader.nextInt();
                languages.put(language, length);
            }
            reader.endObject();
            httpGet.reset();
        } catch (URISyntaxException | IOException e) {
            Message.warn(e.getMessage());
        } finally {
            try {
                if(reader != null)
                    reader.close();
            } catch (IOException e) {
                Message.warn(e.getMessage());
            }
        }
    }

    public void fetchContributors() {
        int page = 1;
        ArrayList<User> contributors = new ArrayList<>();
        CloseableHttpResponse response;
        try {
            Message.println("Start fetching contributors of " + name);
            while(true) {
                CloseableHttpClient client = ConnectionPool.getClient();
                HttpGet httpGet = new HttpGet(new URIBuilder(url)
                        .appendPath("contributors").addParameter("page", String.valueOf(page)).build());
                FineGrainedTokens.addAuthorization(httpGet);
                response = client.execute(httpGet);
                JsonReader reader = new JsonReader(new InputStreamReader(response.getEntity().getContent()));
                reader.beginArray();
                if(!reader.hasNext()) {
                    Message.println("No more contributors of " + name + " at page " + page);
                    break;
                }
                while(reader.hasNext()) {
                    User contributor = readContributor(reader);
//                    JsonStreamReader.updateRepositories(contributor);
                    contributor.fetchRepositories();
                    contributor.fetchOrganizations();
                    contributor.write("./Datas/" + name + "/");
                    contributors.add(contributor);
                }
                reader.endArray();
                page += 1;
                httpGet.reset();
            }
        } catch (URISyntaxException | IOException e) {
            Message.warn(e.getMessage());
        }
        Message.println("Fetch contributors for " + name + " has been finished. " + contributors.size() + " contributors in total.");
        this.contributors = contributors;
    }

    private User readContributor(JsonReader reader) throws IOException {
        String login = null;
        reader.beginObject();
        while(reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals("login")) {
                login = reader.nextString();
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
        if(login == null) {
            Message.warn(name + ".readContributor", "Fail to get user's login.");
            return null;
        }
        User user = new User(login);
        user.updateProfile();
        return user;
    }

    public void fetchContributorsAmount() {
        CloseableHttpResponse response = null;
        try {
            CloseableHttpClient client = ConnectionPool.getClient();
            HttpGet httpGet = new HttpGet(CrawlerUtil.getURI("https://github.com/"+fullName));
            response = client.execute(httpGet);
            Document document = Jsoup.parse(EntityUtils.toString(response.getEntity(), "UTF-8"));
            Elements elements = document.getElementsByAttributeValue("href", "/" + fullName + "/graphs/contributors").select("span");
//            System.out.println("/" + fullName + "/graphs/contributors");
            if (elements.text().length() > 0) {
                this.contributorsCount = Integer.parseInt(elements.text());
            }
            httpGet.reset();
        } catch (IOException | ParseException | URISyntaxException e) {
            Message.warn(e.getMessage());
        }
    }

    public Repository(JsonObject jsonObject){
        this.id = Long.parseLong(String.valueOf(jsonObject.get("id")));
        this.name = CrawlerUtil.trim(String.valueOf(jsonObject.get("name")));
        this.forks = Integer.parseInt(String.valueOf(jsonObject.get("forks")));
        this.openIssues = Integer.parseInt(String.valueOf(jsonObject.get("open_issues")));
        this.watchers = Integer.parseInt(String.valueOf(jsonObject.get("watchers")));
    }
    public Repository(long id){
        this.id = id;
    }
}
